﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace FrogEngine
{
	public class Voxel
	{
		public Voxel(Vector3 bottomCorner, Vector3 topCorner)
		{
			this.Parent = null;
			this.BottomCorner = bottomCorner;
			this.TopCorner = topCorner;
		}

		public Voxel(Voxel parent, Vector3 bottomCorner, Vector3 topCorner)
		{
			this.Parent = parent;
			this.BottomCorner = bottomCorner;
			this.TopCorner = topCorner;
		}

		public Voxel Parent { get; set; }
		public Vector3 BottomCorner { get; set; }
		public Vector3 TopCorner { get; set; }

		public float HalfX { get { return (BottomCorner.X + TopCorner.X) / 2; } }
		public float HalfY { get { return (BottomCorner.Y + TopCorner.Y) / 2; } }
		public float HalfZ { get { return (BottomCorner.Z + TopCorner.Z) / 2; } }

		public IEnumerable<Vector3> GetCorners()
		{
			return new Vector3[] 
			{
				new Vector3 { X = BottomCorner.X, Y = BottomCorner.Y, Z = BottomCorner.Z },
				new Vector3 { X = BottomCorner.X, Y = BottomCorner.Y, Z = TopCorner.Z },
				new Vector3 { X = TopCorner.X, Y = BottomCorner.Y, Z = TopCorner.Z },
				new Vector3 { X = TopCorner.X, Y = BottomCorner.Y, Z = BottomCorner.Z },
				new Vector3 { X = BottomCorner.X, Y = TopCorner.Y, Z = BottomCorner.Z },
				new Vector3 { X = BottomCorner.X, Y = TopCorner.Y, Z = TopCorner.Z },
				new Vector3 { X = TopCorner.X, Y = TopCorner.Y, Z = TopCorner.Z },
				new Vector3 { X = TopCorner.X, Y = TopCorner.Y, Z = BottomCorner.Z }
			};
		}

		public bool IsInside(Vector3 vertex)
		{
			return vertex.X >= BottomCorner.X && vertex.X <= TopCorner.X
				&& vertex.Y >= BottomCorner.Y && vertex.Y <= TopCorner.Y
				&& vertex.Z >= BottomCorner.Z && vertex.Z <= TopCorner.Z;
		}

		public bool IsInside(Triangle triangle)
		{
			return IsInside(triangle.V1) || IsInside(triangle.V2) || IsInside(triangle.V3);
		}

		public bool IsInside(Vertex vertex)
		{
			return IsInside(vertex.Position);
		}

		public bool IsInside(IPrimitive primitive)
		{
			return primitive.Points.Any(a => IsInside(a));
		}

		public IEnumerable<Voxel> Subdivide()
		{
			Voxel[] voxels = new Voxel[8];
			voxels[0] = new Voxel(this, BottomCorner, new Vector3(HalfX, HalfY, HalfZ));
			voxels[1] = new Voxel(this, new Vector3(HalfX, BottomCorner.Y, BottomCorner.Z), new Vector3(TopCorner.X, HalfY, HalfZ));
			voxels[2] = new Voxel(this, new Vector3(HalfX, BottomCorner.Y, HalfZ), new Vector3(TopCorner.X, HalfY, TopCorner.Z));
			voxels[3] = new Voxel(this, new Vector3(BottomCorner.X, BottomCorner.Y, HalfZ), new Vector3(HalfX, HalfY, TopCorner.Z));

			voxels[4] = new Voxel(this, new Vector3(BottomCorner.X, HalfY, BottomCorner.Z), new Vector3(HalfX, TopCorner.Y, HalfZ));
			voxels[5] = new Voxel(this, new Vector3(HalfX, HalfY, BottomCorner.Z), new Vector3(TopCorner.X, TopCorner.Y, HalfZ));
			voxels[6] = new Voxel(this, new Vector3(HalfX, HalfY, HalfZ), TopCorner);
			voxels[7] = new Voxel(this, new Vector3(BottomCorner.X, HalfY, HalfZ), new Vector3(HalfX, TopCorner.Y, TopCorner.Z));
			return voxels;
		}

		/// <summary>
		/// Returns a voxel that represents the intersection of the two voxels (or null if they don't intersect).
		/// </summary>
		/// <param name="otherVoxel"></param>
		/// <returns></returns>
		public Voxel Intersect(Voxel otherVoxel)
		{
			float bx = this.BottomCorner.X;
			float by = this.BottomCorner.Y;
			float bz = this.BottomCorner.Z;
			float tx = this.TopCorner.X;
			float ty = this.TopCorner.Y;
			float tz = this.TopCorner.Z;

			float obx = otherVoxel.BottomCorner.X;
			float oby = otherVoxel.BottomCorner.Y;
			float obz = otherVoxel.BottomCorner.Z;
			float otx = otherVoxel.TopCorner.X;
			float oty = otherVoxel.TopCorner.Y;
			float otz = otherVoxel.TopCorner.Z;

			// check if other min and max points are inside this, and check if this min and max are inside other
			if (obx > bx && oby > by && obz > bz &&
				otx < tx && oty < ty && otz < tz)
			{
				// other voxel is completely inside this voxel
				return new Voxel(null, otherVoxel.BottomCorner, otherVoxel.TopCorner);
			}
			else if (bx > obx && by > oby && bz > obz &&
				tx < otx && ty < oty && tz < otz)
			{
				// this voxel is completely inside other voxel
				return new Voxel(null, this.BottomCorner, this.TopCorner);
			}
			else if (otx > bx && oty > by && otz > bz &&
				otx < tx && oty < ty && otz < tz)
			{
				// other top corner is inside this voxel
				return new Voxel(null, this.BottomCorner, otherVoxel.TopCorner);
			}
			else if (tx > obx && ty > oby && tz > obz &&
				tx < otx && ty < oty && tz < otz)
			{
				// this top corner is inside other voxel
				return new Voxel(null, otherVoxel.BottomCorner, this.TopCorner);
			}
			else if (obx > bx && oby > by && obz > bz &&
				obx < tx && oby < ty && obz < tz)
			{
				// other bottom corner is inside this voxel
				return new Voxel(null, otherVoxel.BottomCorner, this.TopCorner);
			}
			else if (bx > obx && by > oby && bz > obz &&
				bx < otx && by < oty && bz < otz)
			{
				// this bottom corner is inside other voxel
				return new Voxel(null, this.BottomCorner, otherVoxel.TopCorner);
			}
			else return null;
		}

		public bool Intersect(Ray ray)
		{
			return new BoundingBox(this.BottomCorner, this.TopCorner).Intersects(ray) == null ? false : true;
		}
	}
}
